<script setup lang="tsx">
import { createVNode, ref } from 'vue';
import { NodeUtils } from './util.js';
import { ElPopover } from 'element-plus';

const props = defineProps({
  data: {
    type: Object,
    required: true
  },
  // 点击发布时需要校验节点数据完整性 此时打开校验模式
  verifyMode: {
    type: Boolean,
    default: true
  }
});

const emit = defineEmits(['emits']);

const isCondition = (data) => data.type === 'condition';
const isConcurrent = (data) => data.type === 'concurrent';
const notEmptyArray = (arr) => Array.isArray(arr) && arr.length > 0;
const hasBranch = (data) => notEmptyArray(data.conditionNodes);
const hasConditionBranch = (data) => notEmptyArray(data.conditionNodes);
const stopPro = (ev) => ev.stopPropagation();
const hasConcurrentBranch = (data) => {
  return notEmptyArray(data.concurrentNodes);
};

function createNormalCard(conf) {
  const classList = ['flow-path-card'];
  const afterTrue = (isTrue, name) => (isTrue && classList.push(name), isTrue);
  const isStartNode = afterTrue(NodeUtils.isStartNode(conf), 'start-node');
  const isApprNode = afterTrue(NodeUtils.isApproverNode(conf), 'approver');
  const isCopyNode = afterTrue(NodeUtils.isCopyNode(conf), 'copy');
  const isDelayNode = afterTrue(NodeUtils.isDelayNode(conf), 'delay');
  const isTriggerNode = afterTrue(NodeUtils.isTriggerNode(conf), 'trigger');

  return (
    <section
      class={classList.join(' ')}
      onClick={() => {
        eventLancher('edit', conf);
      }}
    >
      <header class="header">
        <div class="title-box" style="height: 100%;width:190px;">
          {isApprNode && <i class="iconfont iconshenpi" style="font-size:12px;color:white;margin-right:4px;"></i>}
          {isCopyNode && <i class="el-icon-s-promotion" style="font-size:12px;color:white;margin-right:4px;"></i>}
          {isDelayNode && <i class="el-icon-s-promotion" style="font-size:12px;color:white;margin-right:4px;"></i>}
          <span class="title-text">{conf.properties.title}</span>
          {!isStartNode && <input vModel_trim={conf.properties.title} class="title-input" style="margin-top:3px;" onClick={stopPro} />}
        </div>
        <div class="actions" style="margin-right:4px;">
          <el-icon
            class="el-icon-close icon"
            onClick={() => {
              eventLancher('deleteNode', conf, props.data);
            }}
          >
            <Close />
          </el-icon>
        </div>
      </header>
      <div class="body">
        <span class="text">{conf.content}</span>
        <div class="icon-wrapper right">
          <el-icon class="el-icon-arrow-right icon right-arrow" >
            <ArrowRight />
          </el-icon>
        </div>
      </div>
    </section>
  );
}

const createFunc = (...arg) => createNormalCard(...arg);
let nodes = {
  start: createFunc,
  approver: createFunc,
  copy: createFunc,
  delay: createFunc,
  trigger: createFunc,
  empty: (_) => '',
  condition: function (conf) {
    // <i
    //    class="el-icon-document-copy icon"
    //    onClick={this.eventLancher.bind(ctx, "copyNode", conf, props.data)}
    //  ></i>
    return (
      <section
        class="flow-path-card condition"
        onClick={() => {
          eventLancher('edit', conf);
        }}
      >
        <header class="header">
          <div class="title-box" style="height: 20px;width:160px;">
            <span class="title-text">{conf.properties.title}</span>
            {<input vModel_trim={conf.properties.title} class="title-input" style="margin-top:1px;" onClick={stopPro} />}
          </div>
          <span class="priority">优先级{conf.properties.priority + 1}</span>
          <div class="actions">
            <el-icon
              class="el-icon-close icon"
              onClick={() => {
                eventLancher('deleteNode', conf, props.data);
              }}
            >
              <Close />
            </el-icon>
          </div>
        </header>
        <div class="body">
          <pre class="text">{conf.content}</pre>
        </div>
        <div
          class="icon-wrapper left"
          onClick={() => {
            eventLancher('increasePriority', conf, props.data);
          }}
        >
          <i class="el-icon-arrow-left icon left-arrow"></i>
        </div>
        <div
          class="icon-wrapper right"
          onClick={() => {
            eventLancher('decreasePriority', conf, props.data);
          }}
        >
          <i class="el-icon-arrow-right icon right-arrow"></i>
        </div>
      </section>
    );
  },
  concurrent: function (conf) {
    // <i
    //    class="el-icon-document-copy icon"
    //    onClick={this.eventLancher.bind(ctx, "copyNode", conf, props.data)}
    //  ></i>
    return (
      <section
        class="flow-path-card concurrent"
        onClick={() => {
          eventLancher('edit', conf);
        }}
      >
        <header class="header">
          <div class="title-box" style="height: 20px;width:160px;">
            <span class="title-text">{conf.properties.title}</span>
            {<input vModel_trim={conf.properties.title} class="title-input" style="margin-top:1px;" onClick={stopPro} />}
          </div>
          <div class="actions">
            <el-icon
              class="el-icon-close icon"
              onClick={() => {
                eventLancher('deleteNode', conf, props.data);
              }}
            >
              <Close />
            </el-icon>
          </div>
        </header>
        <div class="body">
          <pre class="text">{conf.content}</pre>
        </div>
        <div
          class="icon-wrapper left"
          onClick={() => {
            eventLancher('increasePriority', conf, props.data);
          }}
        >
          <el-icon class="el-icon-arrow-left icon left-arrow">
            <ArrowLeft />
          </el-icon>
        </div>
        <div
          class="icon-wrapper right"
          onClick={() => {
            eventLancher('decreasePriority', conf, props.data);
          }}
        >
          <el-icon class="el-icon-arrow-right icon right-arrow">
            <ArrowRight />
          </el-icon>
        </div>
      </section>
    );
  }
};

/* 构造节点
    modify by nbacheng 2023-11-02
*/
function NodeFactory(data) {
  if (!data) return;

  const showErrorTip = props.verifyMode && NodeUtils.checkNode(data) === false;
  const showChildErrorTip =
    props.verifyMode && (NodeUtils.isConditionNode(data) || NodeUtils.isConcurrentNode(data)) && NodeUtils.checkChildNode(data) === false;
  const showErrorDelayTip = props.verifyMode && NodeUtils.checkDelayNode(data) === false;
  let res = [],
    branchNode = '',
    selfNode = null;
  if (showErrorTip) {
    //通用节点的错误检查做特殊处理
    selfNode = (
      <div class="node-wrap">
        <div class={`node-wrap-box ${data.type} ${showErrorTip ? 'error' : ''}`}>
          <el-tooltip content="未设置条件表达式" placement="top" effect="dark">
            <div
              class="error-tip"
              onClick={() => {
                eventLancher('edit', data);
              }}
            >
              !!!
            </div>
          </el-tooltip>
          {nodes[data.type](data)}
          {addNodeButton(data)}
        </div>
      </div>
    );
  } else if (showChildErrorTip) {
    //对并发与条件节点的错误检查做特殊处理
    selfNode = (
      <div class="node-wrap">
        <div class={`node-wrap-box ${data.type} ${showChildErrorTip ? 'error' : ''}`}>
          <el-tooltip content="未设置审批人员节点" placement="top" effect="dark">
            <div
              class="error-tip"
              onClick={() => {
                eventLancher('addApprovalNode', data);
              }}
            >
              !!!
            </div>
          </el-tooltip>
          {nodes[data.type](data)}
          {addNodeButton(data)}
        </div>
      </div>
    );
  } else if (showErrorDelayTip) {
    //对延时节点的错误检查做特殊处理
    selfNode = (
      <div class="node-wrap">
        <div class={`node-wrap-box ${data.type} ${showErrorDelayTip ? 'error' : ''}`}>
          <el-tooltip content="未设置延时时间" placement="top" effect="dark">
            <div
              class="error-tip"
              onClick={() => {
                eventLancher('edit', data);
              }}
            >
              !!!
            </div>
          </el-tooltip>
          {nodes[data.type](data)}
          {addNodeButton(data)}
        </div>
      </div>
    );
  } else {
    //正常节点的显示
    selfNode = (
      <div class="node-wrap">
        <div class={`node-wrap-box ${data.type} ${''}`}>
          {nodes[data.type](data)}
          {addNodeButton(data)}
        </div>
      </div>
    );
  }

  if (hasConditionBranch(data)) {
    // 如果节点是数组 而且是条件分支 添加分支样式包裹
    // {data.childNode && NodeFactory.call(ctx, ctx, data.childNode, h)}

    branchNode = (
      <div class="branch-wrap">
        <div class="branch-box-wrap">
          <div class="branch-box  flex justify-center relative">
            <button
              class="btn"
              onClick={() => {
                eventLancher('appendConditionNode', data);
              }}
            >
              添加条件
            </button>
            {data.conditionNodes.map((d) => NodeFactory(d))}
          </div>
        </div>
        {addNodeButton(data, true)}
      </div>
    );
  }

  if (isCondition(data)) {
    return (
      <div class="col-box">
        <div class="center-line"></div>
        <div class="top-cover-line"></div>
        <div class="bottom-cover-line"></div>
        {selfNode}
        {branchNode}
        {NodeFactory(data.childNode)}
      </div>
    );
  }

  if (hasConcurrentBranch(data)) {
    // 如果节点是数组 而且是并行分支 添加分支样式包裹
    // {data.childNode && NodeFactory.call(ctx, ctx, data.childNode, h)}
    branchNode = (
      <div class="branch-wrap">
        <div class="branch-box-wrap">
          <div class="branch-box  flex justify-center relative">
            <button
              class="btn"
              onClick={() => {
                eventLancher('addConcurrentNode', data);
              }}
            >
              添加并行
            </button>
            {data.concurrentNodes.map((d) => NodeFactory(d))}
          </div>
        </div>
        {addNodeButton(data, true)}
      </div>
    );
  }

  if (isConcurrent(data)) {
    return (
      <div class="col-box">
        <div class="center-line"></div>
        <div class="top-cover-line"></div>
        <div class="bottom-cover-line"></div>
        {selfNode}
        {branchNode}
        {NodeFactory(data.childNode)}
      </div>
    );
  }
  res.push(selfNode);
  branchNode && res.push(branchNode);
  data.childNode && res.push(NodeFactory(data.childNode));

  return res;
}

/* 添加节点按钮类型
    modify by nbacheng 2023-11-01
*/
function addNodeButton(data, isBranch = false) {
  // 只有非条件节点和条件分支树下面的那个按钮 才能添加新分支树
  //let couldAddBranch = !hasConditionBranch(data) || isBranch;
  let isEmpty = data.type === 'empty';
  if (isEmpty && !isBranch) {
    return '';
  }

  return (
    <div class="add-node-btn-box flex  justify-center">
      <div class="add-node-btn">
        <ElPopover
          width={400}
          placement="right"
          trigger="click"
          v-slots={{
            reference: () => (
              <button class="btn" type="button">
                <el-icon className="el-icon-plus icon">
                  <Plus />
                </el-icon>
              </button>
            ),
            default: () => (
              <div class="condition-box">
                <div>
                  <div
                    class="condition-icon"
                    onClick={() => {
                      eventLancher('addApprovalNode', data, isBranch);
                    }}
                  >
                    <i class="el-icon-s-check iconfont"></i>
                  </div>
                  审批人
                </div>

                <div>
                  <div
                    class="condition-icon"
                    onClick={() => {
                      eventLancher('addCopyNode', data, isBranch);
                    }}
                  >
                    <i class="el-icon-s-promotion iconfont" style="vertical-align: middle;"></i>
                  </div>
                  抄送人
                </div>

                <div>
                  <div
                    class="condition-icon"
                    onClick={() => {
                      eventLancher('appendBranch', data, isBranch);
                    }}
                  >
                    <i class="el-icon-share iconfont" style="color:rgb(21, 188, 131);"></i>
                  </div>
                  条件分支
                </div>

                <div>
                  <div
                    class="condition-icon"
                    onClick={() => {
                      eventLancher('addConcurrentBranch', data, isBranch);
                    }}
                  >
                    <i class="el-icon-s-operation iconfont" style="color:rgb(21, 188, 131);"></i>
                  </div>
                  并行分支
                </div>

                <div>
                  <div
                    class="condition-icon"
                    onClick={() => {
                      eventLancher.bind('addDelayNode', data, isBranch);
                    }}
                  >
                    <i class="el-icon-time iconfont" style="color:rgb(21, 188, 131);"></i>
                  </div>
                  延迟等待
                </div>

                <div>
                  <div
                    class="condition-icon"
                    onClick={() => {
                      eventLancher('addTriggerNode', data, isBranch);
                    }}
                  >
                    <i class="el-icon-set-up iconfont" style="color:rgb(21, 188, 131);"></i>
                  </div>
                  触发器
                </div>
              </div>
            )
          }}
        ></ElPopover>
      </div>
    </div>
  );
}

/**
 *事件触发器 统筹本组件所有事件并转发到父组件中
 * @param event
 * @param args
 */
const eventLancher = (event, ...args) => {
  emit('emits', { event, args: args });

  // args.slice(0,-1) vue 会注入MouseEvent到最后一个参数 去除事件对象
  // emit("emits", { event, args: args.slice(0, -1) });
};

const render = () => {
  return (
    <div style="display: inline-flex; flex-direction: column; align-items: center;">
      {props.data && NodeFactory(props.data) }
      <section class="end-node">流程结束</section>
    </div>
  );
};

onMounted(() => {});
</script>

<template>
  <component :is="render()"></component>
</template>

<style lang="scss" scoped>
$bg-color: #f5f5f7;
$line-color: #cacaca;
$base-size: 12px;

@mixin flex-center {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: center;
}

@mixin ellipsis($n) {
  overflow: hidden;
  text-overflow: ellipsis;
  @if $n > 1 {
    display: -webkit-box;
    -webkit-line-clamp: $n;
    -webkit-box-orient: vertical;
  } @else {
    white-space: nowrap;
  }
}

@mixin btn-bigger {
  transform: scale(1.2);
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.1);
}

.node-wrap-box {
  position: relative;
  @include flex-center;
  flex-direction: column;

  &.condition,
  &.concurrent {
    padding: 30px 50px 0;
  }

  &.empty {
    overflow: hidden;
  }

  &.approver::before,
  &.delay::before,
  &.trigger::before {
    content: '';
    position: absolute;
    top: -10px;
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 4px;
    border-style: solid;
    border-width: 8px 6px 4px;
    border-color: $line-color transparent transparent;
    background: $bg-color;
  }

  &.error {
    &.condition .error-tip,
    &.concurrent .error-tip {
      right: 0;
    }

    .error-tip {
      right: -40px;
    }

    .flow-path-card {
      border: 1px solid #f00;

      &:hover {
        border-width: 0;
      }
    }
  }

  .error-tip {
    position: absolute;
    right: 0;
    top: 15%;
    width: 30px;
    height: 30px;
    color: #f00;
    cursor: pointer;
    border-radius: 50%;
    border: 1px solid;
    line-height: 30px;
    transition: right 0.5s;
  }
  &.condition .error-tip,
  &.concurrent .error-tip {
    right: 60px;
  }
}

.end-node {
  font-size: $base-size;
  text-align: center;
  @include flex-center;
  flex-direction: column;
  &::before {
    content: '';
    width: 10px;
    height: 10px;
    margin: auto;
    border: none;
    margin-bottom: 12px;
    border-radius: 50%;
    background: #dbdcdc;
  }
}

.flow-path-card {
  width: 220px;
  min-height: 82px;
  font-size: $base-size;
  border-radius: 4px;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  position: relative;
  box-sizing: border-box;
  box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.3);
  background: #fff;
  &:hover {
    box-shadow:
      0 0 0 2px #3296fa,
      0 0 5px 4px rgba(0, 0, 0, 0.2);
  }
  &.copy .header {
    background-color: #3296fa;
  }
  &.approver:hover,
  &.delay:hover,
  &.trigger:hover {
    box-shadow:
      0 0 0 2px #ff9431,
      0 0 5px 4px rgba(0, 0, 0, 0.2);
  }
  &.approver .header,
  &.delay .header {
    background-color: #ff9431;
  }
  &.trigger .header {
    background-color: rgb(71, 188, 130);
  }
  &.start-node:hover,
  &.start-node .header {
    box-shadow:
      0 0 0 2px #576a95,
      0 0 5px 4px rgba(0, 0, 0, 0.2);
    background-color: #576a95;
  }
  .header {
    padding-left: 16px;
    padding-right: 30px;
    width: 100%;
    height: 24px;
    line-height: 24px;
    color: white;
    position: relative;
    box-sizing: border-box;
    .title-box {
      @include ellipsis(1);
    }
    .title-input {
      position: absolute;
      left: 0;
      border: none;
      background: inherit;
      color: inherit;
      opacity: 0;
      &:focus {
        border-radius: 6px;
        font-size: $base-size;
        padding: 2px;
        padding-left: 4px;
        width: 97%;
        margin-left: 1px;
        height: 18px;
        box-sizing: border-box;
        box-shadow: 0 0 2px 2px #7ec3e1;
        background-color: $bg-color;
        color: black;
        opacity: 1;
      }
    }
    .title-text {
      vertical-align: middle;
    }
    > .actions {
      position: absolute;
      right: 0;
      top: 0;
      visibility: hidden;
    }
  }
  &:not(.start-node):hover {
    .actions {
      visibility: visible;
    }
    .title-text {
      border-bottom: 1px dashed currentColor;
    }
  }
  .body {
    position: relative;
    padding: 12px;
    padding-right: 30px;
    box-sizing: border-box;
    .text {
      margin: 0;
      @include ellipsis(4);
    }
  }
  .icon-wrapper {
    position: absolute;
    top: 0;
    height: 100%;
    width: 14px;
    box-sizing: border-box;
    &.left {
      left: 0;
    }
    &.right {
      right: 0;
    }
    .right-arrow,
    .left-arrow {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
  }
}

.flow-path-card.condition,
.flow-path-card.concurrent {
  padding: 8px 14px;
  .header {
    height: 18px;
    line-height: 18px;
    color: inherit;
    padding: 0;
    .title-text {
      color: #15bc83;
    }
  }
  .body {
    padding: 8px 14px;
  }
  .icon-wrapper:hover {
    background-color: #f1f1f1;
  }
  .right-arrow,
  .left-arrow {
    visibility: hidden;
  }
  &:hover {
    .right-arrow,
    .left-arrow {
      visibility: visible;
    }
    .priority {
      display: none;
    }
  }
}

.col-box:first-of-type .condition .left,
.col-box:first-of-type .concurrent .left,
.col-box:last-of-type .condition .right,
.col-box:last-of-type .concurrent .right {
  display: none;
}

.add-node-btn-box {
  width: 220px;
  height: 100px;
  position: relative;
  padding-top: 30px;
  margin: auto;
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: -1;
    margin: auto;
    width: 2px;
    height: 100%;
    background-color: $line-color;
  }
  .add-node-btn {
    display: flex;
    justify-content: center;
    .btn {
      width: 32px;
      height: 32px;
      border-radius: 15px;
      cursor: pointer;
      outline: none;
      background-color: #3296fa;
      border-color: transparent;
      transition: transform 0.5s;
      &:hover {
        @include btn-bigger;
      }
      .icon {
        color: white;
      }
    }
  }
}

.branch-wrap {
  .branch-box-wrap {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
  }
  .branch-box {
    align-items: stretch;
    border-bottom: 2px solid $line-color;
    border-top: 2px solid $line-color;
    box-sizing: border-box;
    background: $bg-color;
    > .col-box {
      display: flex;
      flex-direction: column;
      align-items: center;
      position: relative;
      &:first-of-type {
        &::before,
        &::after {
          content: '';
          position: absolute;
          left: 0;
          height: 3px;
          width: calc(50% - 1px);
          background: $bg-color;
        }
        &::before {
          top: -2px;
        }
        &::after {
          bottom: -2px;
        }
      }
      &:last-of-type {
        &::before,
        &::after {
          content: '';
          position: absolute;
          right: 0;
          height: 3px;
          width: calc(50% - 1px);
          background: $bg-color;
        }
        &::before {
          top: -2px;
        }
        &::after {
          bottom: -2px;
        }
      }
    }
    .center-line {
      height: 100%;
      width: 2px;
      background: $line-color;
      position: absolute;
    }
    > .btn {
      z-index: 99;
      cursor: pointer;
      position: absolute;
      top: 0;
      outline: none;
      transform: translateY(-50%);
      padding: 6px 10px;
      border: none;
      border-radius: 15px;
      background: white;
      box-shadow: 0 0 10px 0px rgba(0, 0, 0, 0.2);
      transition: transform 0.3s;
      &:hover {
        transform: scale(1.1) translateY(-50%);
      }
    }
  }
}

.condition-box,
.concurrent-box {
  display: flex;
  justify-content: space-around;
  align-items: center;
  text-align: center;
  padding: 10px 20px;

  div:nth-child(1) .iconfont,
  > div:nth-child(2) .iconfont {
    color: #3296fa;
  }
  .condition-icon,
  .concurrent-icon {
    width: 60px;
    height: 60px;
    line-height: 60px;
    border: 1px solid #e5e5e5;
    border-radius: 30px;
    box-sizing: border-box;
    font-size: 12px;
    cursor: pointer;
    margin-bottom: 4px;
    .iconfont {
      font-size: 32px;
    }
    &:hover {
      background: #3296fa;
      box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.1);
      > .iconfont {
        color: white;
      }
    }
  }
}

.relative {
  position: relative;
}

.flex {
  display: flex;
}

.justify-center {
  justify-content: center;
}

.icon {
  vertical-align: middle;
  width: 14px;
  height: 14px;
  font-size: 14px;
}

.priority {
  position: absolute;
  right: 0;
  font-size: 12px;
}
</style>
